/*
 *  linux/kernel/sys.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
参考文档：https://www.junmajinlong.com/linux/euid_ruid/

每个文件都有其所有者和所属组以及权限位属性：

$ ls -l /etc/hosts
-rw-r--r-- 1 root root 619 Oct  1 08:51 /etc/hosts
文件上的用户信息和权限信息是文件属性，是静态的，用来限制谁能对该文件执行什么操作。

而对文件执行操作最终是由进程完成的，所以进程执行操作时拥有的权限才是决定能否对文件执行操作的标准。

Effective UID(EUID)称为有效用户ID，Real UID称为实际用户ID。还有对应的EGID和RGID。它们都是针对进程而非针对文件的，也就是说，它们是进程的属性，而非文件的属性。

进程在执行某些涉及权限的操作时，内核将使用进程的【有效用户/组ID】作为凭证来判断是否有权限执行对应操作。例如，rm命令删除文件a.txt时，内核将使用rm进程的有效用户ID和有效组ID和a.txt文件的用户ID和组ID做比较，进而再比较对应的权限位，从而判断rm进程是否有权限删除a.txt。

既然涉及到权限的操作都以有效ID作为依据，那实际ID有什么作用？RUID和RGID用于确定进程所属的用户和组，主要用于判断是否有权限向进程发送信号：对于非特权用户，如果发送信号的进程A的RUID和目标进程B的RUID，则进程A可以发送信号。由于子进程会继承父进程的实际ID，所以父子进程的RUID相等，父子进程可互相发信号。

用户执行程序时，默认将当前用户或指定的用户(如sudo方式)设置为该进程的有效用户ID和实际用户ID。此时它们是相等的。

进程内部可通过代码修改进程的有效用户ID。例如，以root身份运行的程序，初始时其有效用户ID为0(即root)，具有特权，之后在程序内部修改其有效用户ID为nobody，修改后该进程将失去大量权限。

也可以在程序文件上设置suid，使得该程序在执行时，其有效用户ID被设置为程序文件的UID。

# /bin/passwd设置了SUID，执行passwd时，passwd进程的有效用户ID将设置为root
$ ls -l /bin/passwd  
-rwsr-xr-x 1 root root 68208 Apr 16 20:36 /bin/passwd
举个例子来分析。用户user1执行/bin/app删除a.txt文件时的身份设置过程：只考虑用户不考虑组。

1.获取/bin/app权限信息，判断user1是否有权限执行该程序，若有权，则exec加载成功。
2.如果app文件未设置SUID，则设置进程的有效用户ID和实际用户ID都为user1。
3.如果app文件设置了SUID，则设置进程的有效用户ID为app文件的用户ID。
4.app程序内部可能也会通过系统调用修改有效用户ID。
5.app进程执行删除a.txt文件代码时，内核将比较此时app进程的有效ID和a.txt文件的用户ID，从而判断权限位
此外还有保存的UID(Saved UID)和保存的GID(Saved GID)，保存的ID是保存有效ID的。在程序启动的时候，会将保存的ID设置为有效ID相同的值，之后如果修改有效ID，保存ID将不会改变。

通过ps命令可以查看进程的euid、ruid和suid。

$ ps -o pid,euid,ruid,suid,command 
    PID  EUID  RUID  SUID COMMAND
  26481  1000  1000  1000 -bash
  29120  1000  1000  1000 ps -o pid,euid,ruid,suid,command

*/
#include <errno.h>

#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/kernel.h>
#include <asm/segment.h>
#include <sys/times.h>
#include <sys/utsname.h>

int sys_ftime()
{
	return -ENOSYS;
}

int sys_break()
{
	return -ENOSYS;
}

int sys_ptrace()
{
	return -ENOSYS;
}

int sys_stty()
{
	return -ENOSYS;
}

int sys_gtty()
{
	return -ENOSYS;
}

int sys_rename()
{
	return -ENOSYS;
}

int sys_prof()
{
	return -ENOSYS;
}

// 设置当前进程的实际组ID（rgid）、有效组ID（egid）
	// 如果任务没有超级用户特权，那么只能将egid切换为gid、sgid，或保持不变。
	// 如果任务具有超级用户特权，就能任意设置有效的和实际的用户组 ID。
int sys_setregid(int rgid, int egid)
{
	// 如果任务没有超级用户特权，gid只能保持不变。
	if (rgid>0) {
		if ((current->gid == rgid) || 
		    suser())
			current->gid = rgid;
		else
			return(-EPERM);
	}
	// 如果任务没有超级用户特权，egid只能设置为原先gid、egid、sgid三者之一。即egid要么保持不变，要么设置为gid或sgid的值
	if (egid>0) {
		if ((current->gid == egid) ||
		    (current->egid == egid) ||
		    (current->sgid == egid) ||
		    suser())
			current->egid = egid;
		else
			return(-EPERM);
	}
	return 0;
}

int sys_setgid(int gid)
{
	return(sys_setregid(gid, gid));
}

int sys_acct()
{
	return -ENOSYS;
}

int sys_phys()
{
	return -ENOSYS;
}

int sys_lock()
{
	return -ENOSYS;
}

int sys_mpx()
{
	return -ENOSYS;
}

int sys_ulimit()
{
	return -ENOSYS;
}

// 返回时间戳（秒）。如果 tloc 不为 null, 则时间值也存储在那里。
int sys_time(long * tloc)
{
	int i;

	i = CURRENT_TIME;
	if (tloc) {
		verify_area(tloc,4);
		put_fs_long(i,(unsigned long *)tloc);
	}
	return i;
}

/*
 * Unprivileged users may change the real user id to the effective uid
 * or vice versa.
 */

// 设置任务的实际以及/或者有效的用户ID
	// 如果任务没有超级用户特权，那么只能互换其实际的 uid 和有效的 uid 或保持不变。
	// 如果任务具有超级用户特权，就能任意设置有效的和实际的用户ID。
int sys_setreuid(int ruid, int euid)
{
	int old_ruid = current->uid;
	
	// 如果任务没有超级用户特权，uid只能设置为原先uid、euid之一（ 保持不变 或 互换uid和euid ）。主要用途：将uid设置为euid
	if (ruid>0) {
		if ((current->euid==ruid) ||
            (old_ruid == ruid) ||
		    suser())
			current->uid = ruid;
		else
			return(-EPERM);
	}
	// 如果任务没有超级用户特权，euid只能设置为原先uid、euid之一。
	if (euid>0) {
		if ((old_ruid == euid) ||
            (current->euid == euid) ||
		    suser())
			current->euid = euid;
		else {
			// 事务回滚：euid设置失败时，还原uid
			current->uid = old_ruid;
			return(-EPERM);
		}
	}
	return 0;
}

int sys_setuid(int uid)
{
	return(sys_setreuid(uid, uid));
}

// 设置系统开机时刻时间戳(startup_time)
// 参数 tptr：当前时间的时间戳
int sys_stime(long * tptr)
{
	// 比如是特权用户才能设置
	if (!suser())
		return -EPERM;
	// 当前时间的时间戳 减去 系统已经运行的时间秒值（jiffies/HZ），即是开机时刻的时间戳（秒）。
	startup_time = get_fs_long((unsigned long *)tptr) - jiffies/HZ;
	return 0;
}

// 获取当前任务运行时间统计值到指定用户态变量。函数返回值是系统运行到当前的嘀嗒数。
// tms 结构中包括进程用户运行时间、内核（系统）时间、子进程用户运行时间、子进程系统运行时间。函数返回值是系统运行到当前的嘀嗒数。
int sys_times(struct tms * tbuf)
{
	if (tbuf) {
		verify_area(tbuf,sizeof *tbuf);
		put_fs_long(current->utime,(unsigned long *)&tbuf->tms_utime);
		put_fs_long(current->stime,(unsigned long *)&tbuf->tms_stime);
		put_fs_long(current->cutime,(unsigned long *)&tbuf->tms_cutime);
		put_fs_long(current->cstime,(unsigned long *)&tbuf->tms_cstime);
	}
	return jiffies;
}

int sys_brk(unsigned long end_data_seg)
{
	if (end_data_seg >= current->end_code &&
	    end_data_seg < current->start_stack - 16384)
		current->brk = end_data_seg;
	return current->brk;
}

/*
 * This needs some heave checking ...
 * I just haven't get the stomach for it. I also don't fully
 * understand sessions/pgrp etc. Let somebody who does explain it.
 */
// 为pid进程设置其pgid（如果pid进程是会话首领，不能设置pgid；此外当前进程必须与pid进程属于同一个会话，才能为其能设置pgid）
// 即只能为当前session内其他进程（除leader外）设置pgrp
int sys_setpgid(int pid, int pgid)
{
	int i;

	if (!pid)
		pid = current->pid;
	if (!pgid)
		pgid = current->pid;
	for (i=0 ; i<NR_TASKS ; i++)
		if (task[i] && task[i]->pid==pid) {
			// 任务已经是会话首领 h或 该任务的会话 ID 与当前进程的不同，则也出错返回。
			if (task[i]->leader)
				return -EPERM;
			if (task[i]->session != current->session)
				return -EPERM;
			// 否则设置进程的 pgrp = pgid
			task[i]->pgrp = pgid;
			return 0;
		}
	return -ESRCH;
}

int sys_getpgrp(void)
{
	return current->pgrp;
}

// 创建一个会话(session)（即设置其 leader=1），并且设置其会话号=其组号=其进程号。
// 设置当前进程的session id
int sys_setsid(void)
{
	// 前提：当前进程不是某个session的leader 或 具有超级用户权限
	if (current->leader && !suser())
		return -EPERM;
	current->leader = 1;
	current->session = current->pgrp = current->pid;
	current->tty = -1;
	return current->pgrp;
}

// 获取系统名称等信息。(uname -a)
int sys_uname(struct utsname * name)
{
	static struct utsname thisname = {
		"linux .0","nodename","release ","version ","machine "
	};
	int i;

	if (!name) return -ERROR;
	verify_area(name,sizeof *name);
	// 将 utsname 中的信息逐字节复制到用户缓冲区中。
	for(i=0;i<sizeof *name;i++)
		put_fs_byte(((char *) &thisname)[i],i+(char *) name);
	return 0;
}

// 设置当前进程创建文件属性屏蔽码为 mask ＆ 0777。并返回原屏蔽码。
int sys_umask(int mask)
{
	int old = current->umask;

	current->umask = mask & 0777;
	return (old);
}
